/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import exception.MyException;
import exception.UnifiedExceptionConsole;
import gnu.io.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.completable_future.GetSerialPortDataSupplier;
import util.LoadConfigInfo;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
 * @author wxhntmy
 */
public class SerialPortUtil {

    private final static Logger logger = LoggerFactory.getLogger(SerialPortUtil.class);

    /**
     * 读取配置文件
     */
    private final static LoadConfigInfo loadConfigInfo = new LoadConfigInfo();

    /**
     * 获得系统可用的端口名称列表(COM0、COM1、COM2等等)
     *
     * @return List可用端口名称列表
     */
    @SuppressWarnings("unchecked")
    public static List<String> getSerialPortList() {
        List<String> systemPorts = new ArrayList<>();
        //获得系统可用的端口
        Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            //获得端口的名字
            String portName = portList.nextElement().getName();
            systemPorts.add(portName);
        }
        return systemPorts;
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialOpenAndClose parameter = new SerialOpenAndClose(serialPortName);
        return openSerialPort(parameter);
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @param baudRate       波特率
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName, int baudRate)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialOpenAndClose parameter = new SerialOpenAndClose(serialPortName, baudRate);
        return openSerialPort(parameter);
    }

    /**
     * 打开串口
     *
     * @param serialPortName 串口名称
     * @param baudRate       波特率
     * @param timeout        串口打开超时时间
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(String serialPortName, int baudRate, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        SerialOpenAndClose parameter = new SerialOpenAndClose(serialPortName, baudRate);
        return openSerialPort(parameter, timeout);
    }

    /**
     * 打开串口
     *
     * @param parameter 串口参数
     * @return SerialPort 串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(SerialOpenAndClose parameter)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        return openSerialPort(parameter, 2000);
    }

    /**
     * 打开串口
     *
     * @param parameter 串口参数
     * @param timeout   串口打开超时时间
     * @return SerialPort串口对象
     * @throws NoSuchPortException               对应串口不存在
     * @throws PortInUseException                串口在使用中
     * @throws UnsupportedCommOperationException 不支持操作操作
     */
    public static SerialPort openSerialPort(SerialOpenAndClose parameter, int timeout)
            throws NoSuchPortException, PortInUseException, UnsupportedCommOperationException {
        //通过端口名称得到端口
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(parameter.getSerialPortName());
        //打开端口，（自定义名字，打开超时时间）
        CommPort commPort = portIdentifier.open(parameter.getSerialPortName(), timeout);
        //判断是不是串口
        if (commPort instanceof SerialPort) {
            SerialPort serialPort = (SerialPort) commPort;
            //设置串口参数（波特率，数据位8，停止位1，校验位无）
            serialPort.setSerialPortParams(parameter.getBaudRate(), parameter.getDataBits(), parameter.getStopBits(), parameter.getParity());
            return serialPort;
        } else {
            //是其他类型的端口
            throw new NoSuchPortException();
        }

    }

    /**
     * 检查串口是否打开
     *
     * @param serialPort 串口
     * @return true已打开，false关闭
     */
    public static boolean isOpenSerialPort(SerialPort serialPort) {
        return serialPort != null;
    }

    /**
     * 关闭串口
     *
     * @param serialPort 要关闭的串口对象
     */
    public static void closeSerialPort(SerialPort serialPort) {
        if (serialPort != null) {
            serialPort.close();
        }
    }

    /**
     * 向串口发送数据
     *
     * @param serialPort  串口对象
     * @param data        发送的数据
     * @param charsetName 发送数据的字符编码
     */
    public static void sendData(SerialPort serialPort, byte[] data, String charsetName) {

        OutputStreamWriter oStreamWriter = null;
        try {
            //获得串口的输出流
            oStreamWriter = new OutputStreamWriter(serialPort.getOutputStream(), charsetName);
            oStreamWriter.write(new String(data, charsetName));
            oStreamWriter.flush();
        } catch (IOException e) {
            try {
                throw new MyException("串口发送数据时 IO 异常！");
            } catch (MyException ex) {
                ex.printStackTrace();
            }
        } finally {
            try {
                if (oStreamWriter != null) {
                    oStreamWriter.close();
                }
            } catch (IOException e) {
                try {
                    throw new MyException("关闭串口输出流时 IO 异常！");
                } catch (MyException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 要读取的串口
     * @return 读取的数据
     */
    public static byte[] readData(SerialPort serialPort, ExecutorService executorService) {

        String strBytes = "";

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(
                new GetSerialPortDataSupplier(serialPort), executorService)
                .whenComplete((result, e) -> {
            //执行线程执行完以后的操作。
            //System.out.println(result + " " + e);
            logger.info("接收串口数据异步线程执行完成！");
        }).exceptionally((e) -> {
            //抛出异常
            e.printStackTrace();
            //System.out.println("exception " + e);
            return "Receive SerialPort Data Exception";
        });
        //executorService.shutdown();
        try {
            strBytes = completableFuture.get();
        } catch (Exception e) {
            UnifiedExceptionConsole.console(e);
        }


        try {
            return strBytes.getBytes(loadConfigInfo.getSerialPortReadDataCharsetName());
        } catch (UnsupportedEncodingException e) {
            UnifiedExceptionConsole.console(e);
        }
        return null;
    }

    /**
     * 给串口设置监听
     *
     * @param serialPort serialPort 要读取的串口
     * @param listener   SerialPortEventListener监听对象
     * @throws TooManyListenersException 监听对象太多
     */
    public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) throws TooManyListenersException {
        //给串口添加事件监听
        serialPort.addEventListener(listener);
        //串口有数据监听
        serialPort.notifyOnDataAvailable(true);
        //中断事件监听
        serialPort.notifyOnBreakInterrupt(true);
    }
}
